package net.zoneland.zrdp.common.utils.reflect;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import net.zoneland.zrdp.common.utils.DateUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.zoneland.zrdp.common.core.text.Convert;
import org.springframework.util.ReflectionUtils;

/**
 * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
 *
 * @author zonevue
 */
public class ReflectUtils {
    private static final String SETTER_PREFIX = "set";

    private static final String GETTER_PREFIX = "get";


    private static final Logger LOGGER = LoggerFactory.getLogger(ReflectUtils.class);

    private ReflectUtils() {
        throw new IllegalStateException("Utility class");
    }
    /**
     * 调用Setter方法, 仅匹配方法名。
     * 支持多级，如：对象名.对象名.方法
     */
    public static <E> void invokeSetter(final Object obj, final String propertyName, final E value) {
        Object object = obj;
        final String[] names = StringUtils.split(propertyName, ".");
        for (int i = 0; i < names.length; i++) {
            if (i < names.length - 1) {
                final String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
                object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
            } else {
                final String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
                invokeMethodByName(object, setterMethodName, new Object[] { value });
            }
        }
    }



    /**
     * 直接调用对象方法, 无视private/protected修饰符.
     * 用于一次性调用的情况，否则应使用getAccessibleMethod()函数获得Method后反复调用.
     * 同时匹配方法名+参数类型，
     */
    private static <E> E invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
            final Object[] args) {
        if (obj == null || methodName == null) {
            return null;
        }
        final Method method = getAccessibleMethod(obj, methodName, parameterTypes);
        if (method == null) {
            LOGGER.warn("在 [{}] 中，没有找到 [{}] 方法 ", obj.getClass(), methodName);
            return null;
        }
        try {
            return (E) method.invoke(obj, args);
        } catch (final Exception e) {
            final String msg = "method: " + method + ", obj: " + obj + ", args: " + Arrays.toString(args);
            throw convertReflectionExceptionToUnchecked(msg, e);
        }
    }


    private static final Map<Class<?>, Function<Object, Object>> typeConverters;

    static {
        typeConverters = new HashMap<>();
        typeConverters.put(String.class, arg -> {
            String str = Convert.toStr(arg);
            return StringUtils.endsWith(str, ".0") ? StringUtils.substringBefore(str, ".0") : str;
        });
        typeConverters.put(Integer.class, Convert::toInt);
        typeConverters.put(int.class, Convert::toInt);
        typeConverters.put(Long.class, Convert::toLong);
        typeConverters.put(long.class, Convert::toLong);
        typeConverters.put(Double.class, Convert::toDouble);
        typeConverters.put(double.class, Convert::toDouble);
        typeConverters.put(Float.class, Convert::toFloat);
        typeConverters.put(float.class, Convert::toFloat);
        typeConverters.put(Date.class, arg -> arg instanceof String ? DateUtil.parseDate(arg) : org.apache.poi.ss.usermodel.DateUtil.getJavaDate((Double) arg));
        typeConverters.put(Boolean.class, Convert::toBool);
        typeConverters.put(boolean.class, Convert::toBool);
    }

    /**
     * 直接调用对象方法, 无视private/protected修饰符，
     * 用于一次性调用的情况，否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
     * 只匹配函数名，如果有多个同名函数调用第一个。
     */
    private static void invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
        if (obj == null || methodName == null) {
            return;
        }
        final Method method = getAccessibleMethodByName(obj, methodName, args.length);
        if (method == null) {
            LOGGER.debug("在 [{}] 中，没有找到 [{}] 方法 ", obj.getClass(), methodName);
            return;
        }
        try {
            final Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                if (args[i] != null && !args[i].getClass().equals(parameterTypes[i]) && typeConverters.containsKey(parameterTypes[i])) {
                    args[i] = typeConverters.get(parameterTypes[i]).apply(args[i]);
                }
            }
            method.invoke(obj, args);
        } catch (final Exception e) {
            final String msg = String.format("method: %s, obj: %s, args: %s", method, obj, Arrays.toString(args));
            throw convertReflectionExceptionToUnchecked(msg, e);
        }
    }




    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
     * 如向上转型到Object仍无法找到, 返回null.
     * 匹配函数名+参数类型。
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
     */
    private static Method getAccessibleMethod(final Object obj, final String methodName,
            final Class<?>... parameterTypes) {
        // 为空不报错。直接返回 null
        if (obj == null) {
            return null;
        }
        Validate.notBlank(methodName, "methodName can't be blank");
        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
            try {
                final Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
                makeAccessible(method);
                return method;
            } catch (final NoSuchMethodException e) {
                LOGGER.warn("方法不存在：{},{}", obj.getClass(), methodName);
            }
        }
        return null;
    }

    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
     * 如向上转型到Object仍无法找到, 返回null.
     * 只匹配函数名。
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
     */
    private static Method getAccessibleMethodByName(final Object obj, final String methodName, final int argsNum) {
        // 为空不报错。直接返回 null
        if (obj == null) {
            return null;
        }
        Validate.notBlank(methodName, "methodName can't be blank");
        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
            final Method[] methods = searchType.getDeclaredMethods();
            for (final Method method : methods) {
                if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) {
                    makeAccessible(method);
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * 改变private/protected的方法为public，尽量不调用实际改动的语句，避免JDK的SecurityManager抱怨。
     */
    private static void makeAccessible(Method method) {
        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
                && !method.isAccessible()) {
            ReflectionUtils.makeAccessible(method);
        }
    }

    /**
     * 将反射时的checked exception转换为unchecked exception.
     */
    private static RuntimeException convertReflectionExceptionToUnchecked(final String msg, final Exception e) {
        if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
                || e instanceof NoSuchMethodException) {
            return new IllegalArgumentException(msg, e);
        } else if (e instanceof InvocationTargetException) {
            return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException());
        }
        return new RuntimeException(msg, e);
    }
}
